import datetime
import errno
import logging
import os
import os.path
import platform
import random
import select
import signal
import socket
import subprocess
import sys
import threading
import time
import unittest

import psutil
import werkzeug.serving
from werkzeug.debug import DebuggedApplication
from odoo.tests.common import OdooSuite

if os.name == 'posix':
    pass

else:
   signal.SIGHUP = -1
   inotify = None

if not inotify:
    try:
        import watchdog
        pass
    except ImportError:
        watchdog = None

try:
    from setproctitle import setproctitle
except ImportError:
    setproctitle = lambda x: None

import odoo
from odoo.modules import get_modules
from odoo.modules.module import run_unit_tests, get_test_modules
from odoo.modules.registry import Registry
from odoo.release import nt_service_name
from odoo.tools import config
from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats

_logger = logging.getLogger(__name__)

SLEEP_INTERVAL = 60  # 1 min

def memory_info(process):
    pmem = (getattr(process, 'memory_info', None) or process.get_memory_info)()

    if platform.system() == 'Darwin':
        pass
    return pmem.vms

def set_limit_memory_hard():
    if os.name == 'posix' and config['limit_memory_hard']:
        pass

def empty_pipe(fd):
    pass

class LoggingBaseWSGIServerMixIn(object):
    def handle_error(self, request, client_address):
        pass

class BaseWSGIServerNoBind(LoggingBaseWSGIServerMixIn, werkzeug.serving.BaseWSGIServer):
    def __init__(self, app):
        pass

    def server_activate(self):
        pass

class RequestHandler(werkzeug.serving.WSGIRequestHandler):
    def setup(self):
        if config['test_enable'] or config['test_file']:
            self.timeout = 5
        # flag the current thread as handling a http request
        super(RequestHandler, self).setup()
        me = threading.currentThread()
        me.name = 'odoo.service.http.request.%s' % (me.ident,)

class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer):
    def __init__(self, host, port, app):
        self.max_http_threads = os.environ.get("ODOO_MAX_HTTP_THREADS")
        if self.max_http_threads:
            try:
                self.max_http_threads = int(self.max_http_threads)
            except ValueError:
                # If the value can't be parsed to an integer then it's computed in an automated way to
                # half the size of db_maxconn because while most requests won't borrow cursors concurrently
                # there are some exceptions where some controllers might allocate two or more cursors.
                self.max_http_threads = config['db_maxconn'] // 2
            self.http_threads_sem = threading.Semaphore(self.max_http_threads)
        super(ThreadedWSGIServerReloadable, self).__init__(host, port, app,
                                                           handler=RequestHandler)

        self.daemon_threads = False

    def server_bind(self):
        SD_LISTEN_FDS_START = 3
        if os.environ.get('LISTEN_FDS') == '1' and os.environ.get('LISTEN_PID') == str(os.getpid()):
            self.reload_socket = True
            self.socket = socket.fromfd(SD_LISTEN_FDS_START, socket.AF_INET, socket.SOCK_STREAM)
            _logger.info('HTTP service (werkzeug) running through socket activation')
        else:
            self.reload_socket = False
            super(ThreadedWSGIServerReloadable, self).server_bind()
            _logger.info('HTTP service (werkzeug) running on %s:%s', self.server_name, self.server_port)

    def server_activate(self):
        if not self.reload_socket:
            super(ThreadedWSGIServerReloadable, self).server_activate()

    def process_request(self, request, client_address):
        pass

    def _handle_request_noblock(self):
        pass

    def shutdown_request(self, request):
        pass

class FSWatcherBase(object):
    def handle_file(self, path):
        pass

class FSWatcherWatchdog(FSWatcherBase):
    def __init__(self):
        pass

    def dispatch(self, event):
        pass

    def start(self):
        pass

    def stop(self):
        pass

class FSWatcherInotify(FSWatcherBase):
    def __init__(self):
        pass

    def run(self):
        pass

    def start(self):
        pass

    def stop(self):
        pass

class CommonServer(object):
    def __init__(self, app):
        self.app = app
        self.interface = config['http_interface'] or '0.0.0.0'
        self.port = config['http_port']
        self.pid = os.getpid()

    def close_socket(self, sock):
        pass

class ThreadedServer(CommonServer):
    def __init__(self, app):
        super(ThreadedServer, self).__init__(app)
        self.main_thread_id = threading.currentThread().ident
        self.quit_signals_received = 0
        self.httpd = None
        self.limits_reached_threads = set()
        self.limit_reached_time = None

    def signal_handler(self, sig, frame):
        if sig in [signal.SIGINT, signal.SIGTERM]:
            self.quit_signals_received += 1
            if self.quit_signals_received > 1:
                sys.stderr.write("Forced shutdown.\n")
                os._exit(0)
            raise KeyboardInterrupt()
        elif hasattr(signal, 'SIGXCPU') and sig == signal.SIGXCPU:
            sys.stderr.write("CPU time limit exceeded! Shutting down immediately\n")
            sys.stderr.flush()
            os._exit(0)
        elif sig == signal.SIGHUP:
            odoo.phoenix = True
            self.quit_signals_received += 1
            raise KeyboardInterrupt()

    def process_limit(self):
        memory = memory_info(psutil.Process(os.getpid()))
        if config['limit_memory_soft'] and memory > config['limit_memory_soft']:
            _logger.warning('Server memory limit (%s) reached.', memory)
            self.limits_reached_threads.add(threading.currentThread())

        for thread in threading.enumerate():
            if not thread.daemon or getattr(thread, 'type', None) == 'cron':
                if getattr(thread, 'start_time', None):
                    thread_execution_time = time.time() - thread.start_time
                    thread_limit_time_real = config['limit_time_real']
                    if (getattr(thread, 'type', None) == 'cron' and
                            config['limit_time_real_cron'] and config['limit_time_real_cron'] > 0):
                        thread_limit_time_real = config['limit_time_real_cron']
                    if thread_limit_time_real and thread_execution_time > thread_limit_time_real:
                        _logger.warning(
                            'Thread %s virtual real time limit (%d/%ds) reached.',
                            thread, thread_execution_time, thread_limit_time_real)
                        self.limits_reached_threads.add(thread)
        for thread in list(self.limits_reached_threads):
            if not thread.isAlive():
                self.limits_reached_threads.remove(thread)
        if self.limits_reached_threads:
            self.limit_reached_time = self.limit_reached_time or time.time()
        else:
            self.limit_reached_time = None

    def cron_thread(self, number):
        from odoo.addons.base.models.ir_cron import ir_cron
        while True:
            time.sleep(SLEEP_INTERVAL + number)  # Steve Reich timing style
            registries = odoo.modules.registry.Registry.registries
            _logger.debug('cron%d polling for jobs', number)
            for db_name, registry in registries.items():
                if registry.ready:
                    thread = threading.currentThread()
                    thread.start_time = time.time()
                    try:
                        ir_cron._acquire_job(db_name)
                    except Exception:
                        _logger.warning('cron%d encountered an Exception:', number, exc_info=True)
                    thread.start_time = None

    def cron_spawn(self):
        datetime.datetime.strptime('2012-01-01', '%Y-%m-%d')
        for i in range(odoo.tools.config['max_cron_threads']):
            def target():
                self.cron_thread(i)

            t = threading.Thread(target=target, name="odoo.service.cron.cron%d" % i)
            t.setDaemon(True)
            t.type = 'cron'
            t.start()
            _logger.debug("cron%d started!" % i)

    def http_thread(self):
        def app(e, s):
            return self.app(e, s)

        self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
        self.httpd.serve_forever()

    def http_spawn(self):
        t = threading.Thread(target=self.http_thread, name="odoo.service.httpd")
        t.setDaemon(True)
        t.start()

    def start(self, stop=False):
        _logger.debug("Setting signal handlers")
        set_limit_memory_hard()
        if os.name == 'posix':
            signal.signal(signal.SIGINT, self.signal_handler)
            signal.signal(signal.SIGTERM, self.signal_handler)
            signal.signal(signal.SIGCHLD, self.signal_handler)
            signal.signal(signal.SIGHUP, self.signal_handler)
            signal.signal(signal.SIGXCPU, self.signal_handler)
            signal.signal(signal.SIGQUIT, dumpstacks)
            signal.signal(signal.SIGUSR1, log_ormcache_stats)
        elif os.name == 'nt':
            import win32api
            win32api.SetConsoleCtrlHandler(lambda sig: self.signal_handler(sig, None), 1)

        test_mode = config['test_enable'] or config['test_file']
        if test_mode or (config['http_enable'] and not stop):
            self.http_spawn()

    def stop(self):
        if getattr(odoo, 'phoenix', None):
            _logger.info("Initiating server reload")
        else:
            _logger.info("Initiating shutdown")
            _logger.info("Hit CTRL-C again or send a second signal to force the shutdown.")

        stop_time = time.time()

        if self.httpd:
            self.httpd.shutdown()

        me = threading.currentThread()
        _logger.debug('current thread: %r', me)
        for thread in threading.enumerate():
            _logger.debug('process %r (%r)', thread, thread.isDaemon())
            if (thread != me and not thread.isDaemon() and thread.ident != self.main_thread_id and
                    thread not in self.limits_reached_threads):
                while thread.isAlive() and (time.time() - stop_time) < 1:
                    _logger.debug('join and sleep')
                    thread.join(0.05)
                    time.sleep(0.05)

        _logger.debug('--')
        logging.shutdown()

    def run(self, preload=None, stop=False):
        self.start(stop=stop)

        rc = preload_registries(preload)

        if stop:
            self.stop()
            return rc

        self.cron_spawn()

        try:
            while self.quit_signals_received == 0:
                self.process_limit()
                if self.limit_reached_time:
                    has_other_valid_requests = any(
                        not t.daemon and
                        t not in self.limits_reached_threads
                        for t in threading.enumerate()
                        if getattr(t, 'type', None) == 'http')
                    if (not has_other_valid_requests or
                            (time.time() - self.limit_reached_time) > SLEEP_INTERVAL):
                        _logger.info('Dumping stacktrace of limit exceeding threads before reloading')
                        dumpstacks(thread_idents=[thread.ident for thread in self.limits_reached_threads])
                        self.reload()
                    else:
                        time.sleep(1)
                else:
                    time.sleep(SLEEP_INTERVAL)
        except KeyboardInterrupt:
            pass

        self.stop()

    def reload(self):
        os.kill(self.pid, signal.SIGHUP)

class GeventServer(CommonServer):
    def __init__(self, app):
        pass

    def process_limits(self):
        pass

    def watchdog(self, beat=4):
        pass

    def start(self):
        pass

    def stop(self):
        pass

    def run(self, preload, stop):
        pass


class PreforkServer(CommonServer):
    def __init__(self, app):
        pass

    def pipe_new(self):
        pass

    def pipe_ping(self, pipe):
        pass

    def signal_handler(self, sig, frame):
        pass

    def worker_spawn(self, klass, workers_registry):
        pass

    def long_polling_spawn(self):
        pass

    def worker_pop(self, pid):
        pass

    def worker_kill(self, pid, sig):
        pass

    def process_signals(self):
        pass

    def process_zombie(self):
        pass

    def process_timeout(self):
        pass

    def process_spawn(self):
        pass

    def sleep(self):
        pass

    def start(self):
        pass

    def stop(self, graceful=True):
        pass

    def run(self, preload, stop):
        pass

class Worker(object):
    def __init__(self, multi):
        pass

    def setproctitle(self, title=""):
        pass

    def close(self):
        pass

    def signal_handler(self, sig, frame):
        pass

    def signal_time_expired_handler(self, n, stack):
        pass

    def sleep(self):
        pass

    def check_limits(self):
        pass

    def process_work(self):
        pass

    def start(self):
        pass

    def stop(self):
        pass

    def run(self):
        pass

    def _runloop(self):
        pass

class WorkerHTTP(Worker):
    def __init__(self, multi):
        pass

    def process_request(self, client, addr):
        pass

    def process_work(self):
        pass

    def start(self):
        pass

class WorkerCron(Worker):
    def __init__(self, multi):
        pass

    def sleep(self):
        pass

    def _db_list(self):
        pass

    def process_work(self):
        pass

    def start(self):
        pass

server = None

def load_server_wide_modules():
    server_wide_modules = {'base', 'web'} | set(odoo.conf.server_wide_modules)
    for m in server_wide_modules:
        try:
            odoo.modules.module.load_openerp_module(m)
        except Exception:
            msg = ''
            if m == 'web':
                msg = """
The `web` module is provided by the addons found in the `openerp-web` project.
Maybe you forgot to add those addons in your addons_path configuration."""
            _logger.exception('Failed to load server-wide module `%s`.%s', m, msg)


def load_test_file_py(registry, test_file):
    pass

def _reexec(updated_modules=None):
    pass

def load_test_file_py(registry, test_file):
    pass

def preload_registries(dbnames):
    dbnames = dbnames or []
    rc = 0
    for dbname in dbnames:
        pass
    return rc

def start(preload=None, stop=False):
    global server

    load_server_wide_modules()
    odoo.service.wsgi_server._patch_xmlrpc_marshaller()

    if odoo.evented:
        server = GeventServer(odoo.service.wsgi_server.application)
    elif config['workers']:
        pass
    elif config['workers']:
        if config['test_enable'] or config['test_file']:
            _logger.warning("Unit testing in workers mode could fail; use --workers 0.")

        server = PreforkServer(odoo.service.wsgi_server.application)
        if sys.version_info[:2] == (3, 5):
            # turn on buffering also for wfile, to avoid partial writes (Default buffer = 8k)
            werkzeug.serving.WSGIRequestHandler.wbufsize = -1
    else:
        if platform.system() == "Linux" and sys.maxsize > 2 ** 32 and "MALLOC_ARENA_MAX" not in os.environ:
            try:
                import ctypes
                libc = ctypes.CDLL("libc.so.6")
                M_ARENA_MAX = -8
                assert libc.mallopt(ctypes.c_int(M_ARENA_MAX), ctypes.c_int(2))
            except Exception:
                _logger.warning("Could not set ARENA_MAX through mallopt()")
        server = ThreadedServer(odoo.service.wsgi_server.application)

    watcher = None
    if 'reload' in config['dev_mode'] and not odoo.evented:
        if inotify:
            watcher = FSWatcherInotify()
            watcher.start()
        elif watchdog:
            watcher = FSWatcherWatchdog()
            watcher.start()
        else:
            if os.name == 'posix' and platform.system() != 'Darwin':
                module = 'inotify'
            else:
                module = 'watchdog'
            _logger.warning("'%s' module not installed. Code autoreload feature is disabled", module)
    if 'werkzeug' in config['dev_mode']:
        server.app = DebuggedApplication(server.app, evalex=True)

    rc = server.run(preload, stop)

    if watcher:
        watcher.stop()
    # like the legend of the phoenix, all ends with beginnings
    if getattr(odoo, 'phoenix', False):
        _reexec()

    return rc if rc else 0

def restart():
    pass
